#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#include "LinkedList.h"

void * ll_remove(LinkedList * ll, int idx);

LinkedList * initialize() {
  LinkedList * toReturn = malloc(sizeof(LinkedList));
  if (!toReturn) {
    printf("\nError: Malloc failed.");
    exit(1);
  }
  toReturn->size = 0;
  toReturn->head = NULL;
  toReturn->tail = NULL;
}

void ll_print(LinkedList * ll) {
  if (ll->size == 0) {
    printf("\n\nList is empty.\n\n");
  }
  else { 
    uint64_t i;
    printf("\n\nHead -> ");
    Node * tmp = ll->head;
    for (i = 0; i<ll->size - 1; i++) {
      printf("%lu -> ", (uint64_t) tmp->value);
      tmp = tmp->next;
    }
    printf("%lu -| <- Tail\n\n", (uint64_t) tmp->value);
  }
}

void ll_free(LinkedList * ll) {
  while (ll->size > 0) {
    void * tmp = ll_remove(ll, 0);
  }
  free(ll);
}

void ll_insert(LinkedList * ll, void * value, int idx) {

  if (idx > ll->size) {
    printf("\nError: Attempt to insert at invalid index %d. Max Idx: %d", 
	   idx, ll->size);
    return;
  }

  Node * newNode = malloc(sizeof(Node));
  if (! newNode) {
    printf("Error: Malloc failed.");
    exit(1);
  }

  if (ll->size == 0) {
    newNode->value = value;
    newNode->next = NULL;
    ll->head = newNode;
    ll->tail = newNode;
    ll->size += 1;
  }

  else if (idx == 0) {
    newNode->value = value;
    newNode->next = ll->head;
    ll->head = newNode;
    ll->size += 1;
  }

  else if (idx == ll->size) {
    newNode->value = value;
    newNode->next = NULL;
    ll->tail->next = newNode;
    ll->tail = newNode;
    ll->size += 1;
  }

  else {
    int i;
    Node * before = ll->head;
    for (i = 0; i<idx-1; i++) {
      before = before->next;
    }
    newNode->value = value;
    newNode->next = before->next;
    before->next = newNode;
    ll->size += 1;
  }
}

void ll_push(LinkedList * ll, void * value) {
  ll_insert(ll, value, 0);
}

void ll_append(LinkedList * ll, void * value) {
  ll_insert(ll, value, ll->size);
}

void * ll_remove(LinkedList * ll, int idx) {
  
  if (ll->size == 0) {
    printf("\nError: Remove attempted on empty list.");
    return NULL;
  }
  if (idx >= ll->size) {
    printf("\nError: Remove attempted on invalid index.");
    return NULL;
  }
  else if (ll->size == 1) {
    void * toReturn = ll->head->value;
    free(ll->head);
    ll->head = NULL;
    ll->tail = NULL;
    ll->size = 0;
    return toReturn;
  }
  else if (idx == 0) {
    void * toReturn = ll->head->value;
    ll->size -= 1;
    Node * tmp = ll->head;
    ll->head = ll->head->next;
    free(tmp);
    return toReturn;
  }

  else {
    Node * before = ll->head;
    int i = 0;
    for (i = 0; i<idx - 1; i++) {
      before = before->next;
    }
    Node * tmp = before->next;
    before->next = before->next->next;
    void * toReturn = tmp->value;
    free (tmp);
    ll->size -= 1;
    return toReturn;
  }
}

void * ll_get(LinkedList * ll, int idx) {
  if (idx < 0 || idx >= ll->size) {
    printf("\nError: Attempted get() on invalid index %d. Max Idx: %d", idx, ll->size - 1);
    return;
  }
  int i;
  Node * tmp = ll->head;
  for (i = 0; i<idx; i++) {
    tmp = tmp->next;
  }
  return tmp->value;
}

int ll_size(LinkedList * ll) {
  return ll->size;
}

int ll_isEmpty(LinkedList * ll) {
  return (ll->size == 0);
}


    

